import WebSocket from 'ws';
import Koa from 'koa';
import http, { IncomingMessage } from 'http';
import { v4 as uuidv4 } from 'uuid';
import WebSocketConfig from '@/Config/WebSocket';
/**
 * 因为使用的多进程所有各个进程之间不能直接通信，所以需要通过主进程来转发消息
 * 主进程负责接收来自子进程的消息，然后根据消息内容转发给对应的子进程
 * 子进程负责发送消息给主进程，然后主进程再转发给对应的子进程
 */


/**封装的类型包含自定义值和id */
export interface WebSocketWithId extends WebSocket {
    /**每个新连接的唯一值 */
    uuid: string;
    /**自定义存储数据 */
    data: Record<string, any>;
    pongData: {
        /**失败次数 */
        failCount: number;
        /**最后一次心跳时间 */
        lastTime: number;
    }
}



type verifyInfo = { origin: string; secure: boolean; req: IncomingMessage; };

const rules = new Map<string, (ws: WebSocketWithId, id: string, data: any) => any>();

/**验证方法 */
let verifyClient = (info: verifyInfo): boolean => true;
let YoyoOnConnection = (ws: WebSocketWithId, req: http.IncomingMessage): void => { };
let YoyoOnClose = (ws: WebSocketWithId, code: number, reason: Buffer): void => { };
let YoyoOnMessage = (ws: WebSocketWithId, message: WebSocket.RawData): void => { };
let finish: [Function, typeof WebSocketBase];


export class webSocketFunc {
    /**
     * 终止指定连接
     * @param uuid 连接id
     */
    static terminate(uuid: Array<string>): void {
        if (process.send) process.send({ topic: 'websocket', subtopic: 'terminate', uuid });
    }
    /**
     * 关闭指定连接
     * @param uuid 连接id
     */
    static close(uuid: Array<string>): void {
        if (process.send) process.send({ topic: 'websocket', subtopic: 'close', uuid });
    }
    /**
     * 发送消息(支持过滤指定id)
     * @param data 发送的数据
     * @param filterUUID 过滤uuid
     */
    static sendAll(data: any, filterUUID: Array<string> = []) {
        if (process.send) process.send({ topic: 'websocket', subtopic: 'sendAll', data, filterUUID });
    }

    /**
     * 发送消息(发给指定uuid)
     * @param data 发送的数据
     * @param uuid 指定uuid数组
     */
    static sendTo(data: any, uuid: Array<string>): void;
    /**
     * 发送消息(发给指定uuid)
     * @param data 发送的数据
     * @param uuid 指定uuid
     */
    static sendTo(data: any, uuid: string): void;
    static sendTo(data: any, uuid: string | Array<string>) {
        if (!Array.isArray(uuid)) uuid = [uuid];
        if (process.send) process.send({ topic: 'websocket', subtopic: 'sendTo', data, uuid });
    }
}

/**
 * webSocket基类
 * 处理连接、消息、断开等事件
 */
export default class WebSocketBase extends webSocketFunc {
    public wss: WebSocket.Server;
    static init(
        verify: (info: verifyInfo) => boolean
    ): Promise<WebSocketBase> {
        verifyClient = (info: verifyInfo) => {
            try {
                return verify(info);
            } catch (error: any) {
                logger.error('WebSocket.init() 验证出现错误: ', error);
                return false;
            }
        };
        return new Promise((resolve, reject) => {
            // 调用回调函数
            finish = [resolve, this];
        });
    }

    constructor(wss: WebSocket.Server) {
        super();
        this.wss = wss;
    }

    /**
     * 监听新连接
     * @param callback 连接成功后回调
     */
    onConnection(callback: (ws: WebSocketWithId, req: http.IncomingMessage) => void) {
        YoyoOnConnection = (ws: WebSocketWithId, req: http.IncomingMessage) => {
            try {
                callback(ws, req);
            } catch (error: any) {
                logger.error('onConnection() 出现错误: ', error);
            }
        }
    }

    /**
     * 监听断开连接
     * @param callback 断开连接后回调
     */
    onClose(callback: (ws: WebSocketWithId, code: number, reason: Buffer) => void) {
        YoyoOnClose = (ws: WebSocketWithId, code: number, reason: Buffer) => {
            try {
                callback(ws, code, reason);
            } catch (error: any) {
                logger.error('onClose() 出现错误: ', error);
            }
        }
    }

    /**
     * 监听消息
     * @param callback 消息回调
     */
    onMessage(callback: (ws: WebSocketWithId, message: WebSocket.RawData) => void) {
        YoyoOnMessage = (ws: WebSocketWithId, message: WebSocket.RawData) => {
            try {
                callback(ws, message);
            } catch (error: any) {
                logger.error('onMessage() 出现错误: ', error);
            }
        }
    }

    /**
     * 监听方法(对应方法的json的data就会传递给你)
     * 如果对方携带 id 您return只要不是 undefined 就会自动回复 {resultId:xxx,data:xxx}
     * @param ws
     * @param rule 规则(xxxx) {method:'xxxx',data:{}}
     * @param callback 
     */
    onMethod(rule: string, callback: (ws: WebSocketWithId, id: string, data: any) => any) {
        rules.set(rule, callback);
    }

}

/**
 * 主进程初始化
 */
export function webSocketMainInit(cluster: any) {
    if (!WebSocketConfig.status) return;// 如果没有开启则不执行
    /**
     * 向所有子进程广播消息
     * @param message 
     */
    function broadcastToWorkers(message: any) {
        for (let id in cluster.workers) {
            if (cluster.workers.hasOwnProperty(id)) {
                cluster.workers[id].send(message);
            }
        }
    }

    // 接收来自子进程的消息
    cluster.on('message', (worker: any, message: any) => {
        if (message?.topic !== 'websocket') return; // 只处理 websocket 主题的消息
        switch (message.subtopic) {
            case 'sendAll':
                broadcastToWorkers({ topic: 'websocket', subtopic: message.subtopic, data: message.data, filterUUID: message.filterUUID });
                break;
            case 'sendTo':
                broadcastToWorkers({ topic: 'websocket', subtopic: message.subtopic, data: message.data, uuid: message.uuid });
                break;
            case 'close':
                broadcastToWorkers({ topic: 'websocket', subtopic: message.subtopic, uuid: message.uuid });
                break;
            case 'terminate':
                broadcastToWorkers({ topic: 'websocket', subtopic: message.subtopic, uuid: message.uuid });
                break;

            default:
                break;
        }

        // // 回复子进程
        // worker.send(`自己的事情，自己解决，别找你爸爸我！`);
    });
}


/**
 * 子进程初始化
 */
export function WebSocketUse(server: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>) {
    // 如果没有开启则不执行 但是还是需要返回 中间件函数
    if (!WebSocketConfig.status) return async (ctx: Koa.Context, next: Koa.Next) => await next();
    // 引用用户定义的 WebSocket 模块
    try {
        require('/App/WebSocket');
    } catch (error) {
        logger.error('[/App/WebSocket] WebSocket模块出现错误: ', error);
    }



    // 创建 WebSocket 服务器，并设置 verifyClient 选项
    const wss = new WebSocket.Server({
        server,
        verifyClient
    });
    let pongSetIntervalId: NodeJS.Timeout | null = null;// 心跳检测定时器ID

    finish[0](new finish[1](wss));
    wss.on('connection', function (ws: WebSocketWithId, req) {

        Object.defineProperty(ws, 'uuid', {
            value: uuidv4(),
            writable: false,
            configurable: false,
            enumerable: true
        }); // 为每个连接生成唯一的ID
        ws.data = {};// 每个连接的数据(初始化)
        ws.pongData = {
            failCount: 0,// 失败次数
            lastTime: Date.now()// 最后一次收到pong的时间
        };// 每个连接的pong数据(初始化)
        YoyoOnConnection(ws, req);

        ws.on('message', async (message) => {
            const str = message.toString('utf-8');
            try {
                const strJson = JSON.parse(str);
                if (strJson.method && rules.has(strJson.method)) {
                    const callback = rules.get(strJson.method);
                    if (typeof callback === 'function') {
                        const res = callback(ws, strJson?.id, strJson?.data);
                        if (isPromise(res)) {
                            // 异步兼容
                            let result = await res;
                            const eventId = strJson?.id;
                            if ((typeof eventId == 'string' || typeof eventId == 'number') && typeof result !== 'undefined') {
                                // 回调函数返回值不为 undefined 时，自动回复消息(直接回复不需要广播)
                                ws.send(JSON.stringify({ resultId: eventId, data: result }));
                            }
                        }else{
                            // 同步兼容
                            const eventId = strJson?.id;
                            if ((typeof eventId == 'string' || typeof eventId == 'number') && typeof res !== 'undefined') {
                                // 回调函数返回值不为 undefined 时，自动回复消息(直接回复不需要广播)
                                ws.send(JSON.stringify({ resultId: eventId, data: res }));
                            }
                        }

                    }
                }
            } catch (error) { }
            YoyoOnMessage(ws, message);
            // ws.send(`Hello, client!message: ${message} ${process.pid}`);
        });
        ws.on('close', (code, reason) => {
            YoyoOnClose(ws, code, reason);
        });
        ws.on('pong', () => {
            // 收到pong，更新最后收到pong的时间，重置失败次数
            ws.pongData.lastTime = Date.now();
            ws.pongData.failCount = 0;
        });
        // 新的连接进来了
        if (!pongSetIntervalId) pongSetInterval(); // 如果没有定时器，则启动定时器

    });
    // 心跳检测
    function pongSetInterval() {
        const clients = wss.clients;
        clients.forEach((ws: any) => {
            if (ws.readyState !== WebSocket.OPEN) return;// 如果连接已关闭，则不进行心跳检测
            if (!ws.pongData) return;// 如果没有pong数据，则不进行心跳检测
            if (ws.pongData?.failCount >= WebSocketConfig.heartbeatFailCount) {
                logger.warn('[心跳] WebSocket连接超过上限 PID: %d UUID: %s', process.pid, ws.uuid);
                ws.terminate();
                return;
            }
            if ((ws.pongData.lastTime + WebSocketConfig.heartbeatTime * 1000) < Date.now()) {
                ws.pongData.failCount++;
                ws.ping();
                ws.pongData.lastTime = Date.now();
            }
        });
        if (clients.size <= 0) {
            if (pongSetIntervalId) clearTimeout(pongSetIntervalId);
            pongSetIntervalId = null;
            return; // 如果没有连接，则不进行心跳检测
        }
        pongSetIntervalId = setTimeout(pongSetInterval, 2000);
    }

    logger.debug('WebSocket服务器已启动 PID: %d', process.pid);

    /**
     * 发送消息给所有客户端（过滤部分uuid）
     * @param data 要发送的数据
     * @param filterUUID 过滤的uuid
     */
    function wsSendAll(data: any, filterUUID: Array<string> = []) {
        for (const ws of wss.clients) {
            if (filterUUID.includes(String((ws as WebSocketWithId).uuid))) continue;
            if (ws.readyState !== WebSocket.OPEN) continue;// 如果连接已关闭，则不发送消息
            ws.send(JSON.stringify(data));
        }
    }
    /**
     * 向指定uuid发送数据
     * @param data 要发送的数据
     * @param uuid 发送给的uuid
     */
    function wsSendTo(data: any, uuid: Array<string>) {
        for (const ws of wss.clients) {
            if (!uuid.includes(String((ws as WebSocketWithId).uuid))) continue;
            if (ws.readyState !== WebSocket.OPEN) continue;// 如果连接已关闭，则不发送消息
            ws.send(JSON.stringify(data));
        }
    }

    /**
     * 关闭指定uuid连接
     * @param uuid 要关闭的uuid
     */
    function wsCloseTo(uuid: Array<string>) {
        for (const ws of wss.clients) {
            if (!uuid.includes(String((ws as WebSocketWithId).uuid))) continue;
            ws.close();
        }
    }

    /**
     * 终止指定uuid连接
     * @param uuid 要终止的uuid
     */
    function wsTerminateTo(uuid: Array<string>) {
        for (const ws of wss.clients) {
            if (!uuid.includes(String((ws as WebSocketWithId).uuid))) continue;
            ws.terminate();
        }
    }

    // 接收来自主进程的消息
    process.on('message', (message) => {
        const topic = (message as any)?.topic;
        const subtopic = (message as any)?.subtopic;
        if (!topic || !subtopic) return;
        if (topic !== 'websocket') return;
        const data = (message as any)?.data;
        const filterUUID: Array<string> = (message as any)?.filterUUID;
        const uuid: Array<string> = (message as any)?.uuid;
        switch (subtopic) {
            case 'sendAll':
                // 处理 send 子主题的消息
                wsSendAll(data, filterUUID);
                break;
            case 'sendTo':
                // 处理 sendTo 子主题的消息
                if (!uuid || !Array.isArray(uuid)) break;
                wsSendTo(data, uuid);
                break;
            case 'close':
                // 处理 close 子主题的消息
                wsCloseTo(uuid);
                break;
            case 'terminate':
                // 处理 terminate 子主题的消息
                wsTerminateTo(uuid);
                break;
            default:
                break;
        }

    });

    global.WS = webSocketFunc; // 将 WebSocket 函数挂载到全局对象上

    return async (ctx: Koa.Context, next: Koa.Next) => {
        await next();
    };
}



/**
 * 判断是不是Promise对象
 * @param {Any} obj
 * @returns
 */
function isPromise(obj: any): boolean {
    try {
        return obj instanceof Promise;
    } catch (error) {
        return false;
    }
}